#include "cclient.h"

#include <stdlib.h>
#include <X11/Xatom.h>
#include <X11/Xregion.h>

#include <buola/gl.h>

#include <buola/gui/x11/ccomposite.h>
#include <buola/gui/x11/cx11toolkit.h>
#include <buola/gui/x11/cglx.h>

#include <buola/gl/ctexture.h>
#include <buola/algorithm/container.h>
#include "cdecoration.h"
#include "scompositecontext.h"
#include "ceventmanager.h"
#include "scenerenderer/cscenerenderer.h"
#include "cconfiguration.h"
#include <buola/gui/cicon.h>
#include "cmanagedclient.h"

/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// List of window properties, and the way they are used
//
//   O - also valid for override-redirect windows
//   R - read on map request (map notify for override-redirect windows)
//   U - updated by window manager
//   C - changed by client
//   M - changed by client (with a client message to the root window)
//   T - set by taskbar or other tools
//   W - unset on withdraw by us
//   L - left on termination
//
// WM_NAME                        -   R   -   C   -   -   -   -
// WM_ICON_NAME                   -   R   -   C   -   -   -   -
// WM_NORMAL_HINTS                -   R   -   C   -   -   -   -
// WM_HINTS                       -   R   -   -   -   -   -   -
// WM_CLASS                       -   R   -   -   -   -   -   -
// WM_TRANSIENT_FOR               O   R   -   -   -   -   -   -
// WM_PROTOCOLS                   -   R   -   -   -   -   -   -
// WM_COLORMAP_WINDOWS                                                ignored
// WM_CLIENT_MACHINE              -   R   -   -   -   -   -   -
// WM_STATE                       -   -   U   -   M   -   W   -
// WM_ICON_SIZE                   -   -   U   -   -   -   -   -
// 
// _NET_WM_NAME                   -   R   -   C   -   -   -   -       (UTF8)
// _NET_WM_VISIBLE_NAME           -   -   U   -   -   -   -   -       (UTF8)
// _NET_WM_ICON_NAME              -   R   -   C   -   -   -   -       (UTF8)
// _NET_WM_VISIBLE_ICON_NAME      -   -   U   -   -   -   -   -       (UTF8)
// _NET_WM_DESKTOP                -   R   U   -   M   -   W   L
// _NET_WM_WINDOW_TYPE            O   R   -   -   -   -   -   -
// _NET_WM_STATE                  -   R   U   -   M   -   W   L
// _NET_WM_ALLOWED_ACTIONS        -   -   U   -   -   -   -   -
// _NET_WM_STRUT                  -   R   -   C   -   -   -   -       (ignored if STRUT_PARTIAL is set)
// _NET_WM_STRUT_PARTIAL          -   R   -   C   -   -   -   -       (newer version of STRUT)
// _NET_WM_ICON_GEOMETRY          -   -   -   -   -   T   -   -
// _NET_WM_ICON                   -   R   -   C   -   -   -   -       (not clear in the spec)
// _NET_WM_PID                    -   R   -   C   -   -   -   -       (not clear in the spec)
// _NET_WM_HANDLED_ICONS          -   -   -   -   -   T   -   -       (set only in a window that displays icons)
// _NEW_WM_USER_TIME              -   R   -   C   -   -   -   -
// _NET_WM_USER_TIME_WINDOW       -   R   -   -   -   -   -   -
// _NET_FRAME_EXTENTS             -   -   U   -   -   -   -   -       (size of borders)
//
// _NET_WM_WINDOW_OPACITY         O   R   -   C   -   -   -   -       (not found in any spec?)

namespace buola { namespace bwm {

CClient::CClient(Window pID,const XWindowAttributes &pXAttrs)
    :   mDecoration(nullptr)
    ,   mID(pID)
    ,   mZPriority(ZP_UNKNOWN)  
    ,   mDepth(pXAttrs.depth)
    ,   mVisual(pXAttrs.visual)
    ,   mBorderWidth(pXAttrs.border_width)
    ,   mXPos(pXAttrs.x,pXAttrs.y)
    ,   mPhysicalSize(pXAttrs.width+2*pXAttrs.border_width,pXAttrs.height+2*pXAttrs.border_width)
    ,   mPhysicalPos(pXAttrs.x,pXAttrs.y)
    ,   mState(EState::NONE)
    ,   mFlags(FLAG_NONE)
    ,   mStackingOrder(-1)
    ,   mAnimating(ANIM_NONE)
    ,   mPictFormat(nullptr)
    ,   mPixmap(0)
    ,   mXDamage(0)
    ,   mFadeInfo(nullptr)
    ,   mPrevTrans(nullptr)
{
    mTexture.mBound=false;
}

CClient::~CClient()
{
    if (mXDamage)
        XDamageDestroy(gui::x11::get_default_x11_display()->GetDisplay(),mXDamage);

    gRoot->OnClientRemoved(this);

    DestroyDecoration();
    DiscardPixmap();
}

void CClient::StartComposer()
{
    if(GetFlag(FLAG_COMPOSED)) return;

    mXDamage=XDamageCreate(gui::x11::get_default_x11_display()->GetDisplay(),
                            mID, XDamageReportRawRectangles);

    CalcShape();

    mEffOpacity=mOpacity;

    DetermineMode();
    DetermineRegions();

    SetFlag(FLAG_COMPOSED);

    gRoot->mEventManager->SelectComposerEvents(this);
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                  management of other classes                  //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::DestroyDecoration()
{
    delete mDecoration;
    mDecoration=nullptr;
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                    client window operations                   //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::MoveClient(const CPoint_i &pPos) //without border
{
//  msg_info() << "MoveClient " << mID << "\n";

    mXPos=pPos+mVirtualViewportOffset[gRoot->GetActiveDesktop()];

    XMoveWindow(gui::x11::get_default_x11_display()->GetDisplay(),mID,mXPos.x,mXPos.y);

    mPhysicalPos=pPos;

    OnGeometryChanged(false);
}

void CClient::MoveResizeClient(const CPoint_i &pPos,const CSize_i &pSize)
{
//  msg_info() << "MoveResizeClient " << mID << "\n";

    mXPos=pPos+mVirtualViewportOffset[gRoot->GetActiveDesktop()];

    XMoveResizeWindow(gui::x11::get_default_x11_display()->GetDisplay(),mID,mXPos.x,mXPos.y,
                                pSize.x,pSize.y);

    mPhysicalPos=pPos;
    mPhysicalSize=pSize+2*mBorderWidth;

    if(!GetFlag(FLAG_WAITINGRESIZEALARM))
        OnGeometryChanged(true);
}

void CClient::ResizeClient(const CSize_i &pSize)
{
    XResizeWindow(gui::x11::get_default_x11_display()->GetDisplay(),mID,pSize.x,pSize.y);

    mPhysicalSize=pSize+2*mBorderWidth;

    if(!GetFlag(FLAG_WAITINGRESIZEALARM))
        OnGeometryChanged(true);
}

void CClient::ShowClient(bool pShow)
{
    if(pShow)
    {
        XMapWindow(gui::x11::get_default_x11_display()->GetDisplay(),mID);
        SetFlag(FLAG_MAPPED);
    }
    else
    {
        XUnmapWindow(gui::x11::get_default_x11_display()->GetDisplay(),mID);
        UnsetFlag(FLAG_MAPPED);
    }
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                   window manager operations                   //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::SetOpacity(double pOpacity)
{
    mOpacity=pOpacity;
    if(mDecoration)
        mDecoration->OnOpacityChanged();
    StartAnimation(ANIM_FADECHANGE);
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                     accessing properties                      //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::ReadCommonProperties()
{
    mWindowType=gui::x11::icccm::read_net_wm_window_type(mID);
    mTransientFor=gui::x11::icccm::read_wm_transient_for(mID);
    if(!gui::x11::icccm::read_net_wm_desktop(mID,mDesktop))
        mDesktop=gRoot->GetActiveDesktop();
    mOpacity=gui::x11::icccm::read_net_wm_window_opacity(mID);
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                    internal calculations                      //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::CalcShape()
{
    msg_info() << "calculating shape\n";
    int lShaped=0,lD2,lD3,lD6,lD7,lD8;
    unsigned int lD4,lD5,lD9,lD10;
    XShapeQueryExtents(gui::x11::get_default_x11_display()->GetDisplay(),mID,&lShaped,&lD2,&lD3,
                       &lD4,&lD5,&lD6,&lD7,&lD8,&lD9,&lD10);
    mShaped=lShaped;
    if(mShaped)
    {
        int lCount,lOrdering;
        XRectangle *lRects=XShapeGetRectangles(gui::x11::get_default_x11_display()->GetDisplay(),mID,ShapeBounding,
                                           &lCount,&lOrdering);

        mRegions.mShape.clear();
        
        for(int i=0;i<lCount;i++)
            mRegions.mShape|=CRect_i(CPoint_i(lRects[i].x,lRects[i].y),CSize_i(lRects[i].width,lRects[i].height));
    }
}

void CClient::CalcDesktops()
{
    for(int i=0;i<gRoot->GetDesktopCount();i++)
    {
        if(IsOnAllDesktops())
        {
            mVirtualViewportOffset[i]={0,0};

            mOnDesktop[i]=true;
        }
        else
        {
            mVirtualViewportOffset[i]=
                    gRoot->GetVirtualViewportOffset(mDesktop,i);

            CRect_i lRect(mPhysicalPos+mVirtualViewportOffset[i],
                        mPhysicalSize);

            mOnDesktop[i]=lRect.DoesIntersect(CRect_i(CPoint_i(0,0),
                                    CSize_i(gRoot->GetSize())));
        }
    }
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                       event handlers                          //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::OnClientReparented()
{
    StartAnimation(ANIM_FADEOUT);
}

void CClient::OnShapeChanged()
{
    CalcShape();

    if(mDecoration)
        mDecoration->OnShapeChanged();

    DetermineRegions();
    DamageWhole();
}

void CClient::OnDesktopSwitched()
{
    MoveClient(mPhysicalPos);
}

void CClient::OnDesktopsChanged()
{
    CalcDesktops();
}

void CClient::OnGeometryChanged(bool pSize)
{
    CalcDesktops();

    if(mDecoration)
        mDecoration->OnGeometryChanged(pSize,mPhysicalPos,mPhysicalSize,mXPos);

    if (pSize)
        DiscardPixmap();
    DamageWhole();

    DetermineRegions();
    DetermineMatrix();

    DamageWhole();

}

bool CClient::CompareByStack(CClient *p1,CClient *p2)
{
    int lP1=p1->mZPriority;
    int lP2=p2->mZPriority;

    if(lP1!=lP2)
        return lP1<lP2;
    else if(p1->mStackingOrder!=p2->mStackingOrder)
        return p1->mStackingOrder<p2->mStackingOrder;
    else
        return p1<p2;
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                       composite manager                       //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::CreatePixmap()
{
//  msg_info() << "Creating pixmap for ID " << mID << "\n";
    mPixmap=gui::x11::CComposite::NamePixmap(mID);
}

void CClient::DiscardPixmap()
{
//  msg_info() << "Discarding pixmap for ID " << mID << "\n";
    if(mTexture.mBound)
    {
        glEnable(mTexture.mTexture.Target());
        mTexture.mTexture.Bind();
        glXReleaseTexImageEXT(gui::x11::get_default_x11_display()->GetDisplay(),
                              mTexture.mGLXPixmap,GLX_FRONT_LEFT_EXT);
        mTexture.mTexture.UnBind();
        glDisable(mTexture.mTexture.Target());
        glXDestroyGLXPixmap(gui::x11::get_default_x11_display()->GetDisplay(),mTexture.mGLXPixmap);
        mTexture.mGLXPixmap=None;
        mTexture.mBound=false;
    }
    if(mPixmap)
    {
        XFreePixmap (gui::x11::get_default_x11_display()->GetDisplay(), mPixmap);
        mPixmap = None;
    }
}

bool CClient::CreateTexture()
{
    if(mTexture.mBound) return true;

    if (!mPixmap)
        CreatePixmap();

    gui::x11::CGLX::SFBConfig *lConfig=gui::x11::CGLX::GetConfigForDepth(mDepth);

    if(!lConfig)
        throw XInternal("no GL config for depth");

    int lAttribs[]={
        GLX_TEXTURE_TARGET_EXT,GLX_TEXTURE_2D_EXT,
        GLX_TEXTURE_FORMAT_EXT,lConfig->mTextureFormat,
        GLX_MIPMAP_TEXTURE_EXT,lConfig->mMipMap,
        None
    };

    mTexture.mGLXPixmap=glXCreatePixmap(gui::x11::get_default_x11_display()->GetDisplay(),
                                        lConfig->mConfig,mPixmap,lAttribs);

    if(!mTexture.mGLXPixmap)
    {
        msg_warn() << "can't bind texture!!!\n";
        return false;
    }

    mTexture.mMatrix(0,0)=1.0f/mPhysicalSize.x;
    mTexture.mMatrix(0,2)=0.0f;
    if(lConfig->mYInverted)
    {
        mTexture.mMatrix(1,1)=1.0f/mPhysicalSize.y;
        mTexture.mMatrix(1,2)=0.0f;
    }
    else
    {
        mTexture.mMatrix(1,1)=-1.0f/mPhysicalSize.y;
        mTexture.mMatrix(1,2)=1.0f;
    }

    mTexture.mTexture.CreateWithTarget(GL_TEXTURE_2D);
    glXBindTexImageEXT(gui::x11::get_default_x11_display()->GetDisplay(),mTexture.mGLXPixmap,
                       GLX_FRONT_LEFT_EXT,nullptr);

    mTexture.mFilter=GL_NEAREST;

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
///\todo
/*
    if(mFlags&FLAG_AUTOMIPMAPS)
    {
        glTexParameteri(mTarget,GL_GENERATE_MIPMAP,GL_TRUE);
        mFlags&=~FLAG_OLDMIPMAPS;
    }
*/
///\todo    mWrap=GL_CLAMP_TO_EDGE;

    mTexture.mTexture.UnBind();

    DetermineMatrix();
    mTexture.mBound=true;
    return true;
}

void CClient::EnableTexture(bool pFastFilter,bool pMipMaps)
{
    GLenum lTarget=mTexture.mTexture.Target();
    glEnable(lTarget);
    mTexture.mTexture.Bind();

    if(pFastFilter)
    {
        if(mTexture.mFilter!=GL_NEAREST)
        {
            glTexParameteri(lTarget,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
            glTexParameteri(lTarget,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

            mTexture.mFilter=GL_NEAREST;
        }
    }
///\todo
/*    else if(pMipMaps)
    {
        if(mFlags&FLAG_OLDMIPMAPS)
        {
            glGenerateMipmapEXT(mTarget);
            mFlags&=~FLAG_OLDMIPMAPS;
        }

        if(mFilter!=GL_LINEAR_MIPMAP_LINEAR)
        {
            glTexParameteri(mTarget,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);

            if(mFilter!=GL_LINEAR)
                glTexParameteri(mTarget,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

            mFilter=GL_LINEAR_MIPMAP_LINEAR;
        }
    }
*/
    else
    {
        if(mTexture.mFilter!=GL_LINEAR)
        {
            glTexParameteri(lTarget,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
            glTexParameteri(lTarget,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

            glTexParameteri(lTarget,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_BORDER);
            glTexParameteri(lTarget,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_BORDER);

            glTexParameterf(lTarget,GL_TEXTURE_MAX_ANISOTROPY_EXT,16.0);

            mTexture.mFilter=GL_LINEAR;
        }
    }
}

void CClient::DisableTexture()
{
    mTexture.mTexture.UnBind();
    glDisable(mTexture.mTexture.Target());
}

void CClient::DetermineMode()
{
    /* if trans prop == -1 fall back on  previous tests*/

    if(!mPictFormat)
    {
        mPictFormat=XRenderFindVisualFormat (gui::x11::get_default_x11_display()->GetDisplay(),mVisual);
    }

    if(mPictFormat&&mPictFormat->type==PictTypeDirect&&
                     mPictFormat->direct.alphaMask)
    {
        mCompositeMode = COMPOSITE_ARGB;
    }
    else if(mEffOpacity!=1.0)
    {
        mCompositeMode = COMPOSITE_TRANS;
    }
    else
    {
        mCompositeMode=COMPOSITE_SOLID;
    }
}

void CClient::DetermineMatrix()
{
    for(int i=0;i<gRoot->GetDesktopCount();i++)
    {
        mMatrix[i]=mTexture.mMatrix;
        mMatrix[i](0,2)-=(mPhysicalPos.x+mVirtualViewportOffset[i].x)*mMatrix[i](0,0);
        mMatrix[i](1,2)-=(mPhysicalPos.y+mVirtualViewportOffset[i].y)*mMatrix[i](1,1);
    }
}

void CClient::DetermineRegions()
{
    CRect_i lClientRect(mPhysicalPos,mPhysicalSize);
    CRect_i lDecoRect;
    if(mDecoration)
    {
        lDecoRect=lClientRect+mDecoration->GetTotalExtent();
    }
    else
        lDecoRect=lClientRect;

    for(int i=0;i<gRoot->GetDesktopCount();i++)
    {
        if(!IsOnDesktop(i)) continue;
        if(mShaped)
            mRegions.mClient[i]=mRegions.mShape+(mPhysicalPos+mVirtualViewportOffset[i]);
        else
            mRegions.mClient[i]=lClientRect+mVirtualViewportOffset[i];
        mRegions.mWhole[i]=lDecoRect+mVirtualViewportOffset[i];
    }
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                     painting and damage                       //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::DrawGeometry(const CRegion &pR,int pDesktop)
{
    int lVSize=36*pR.size();
    GLfloat lVertices[lVSize];

    GLfloat *lV=lVertices;
    
    for(auto r : pR)
    {
        *(lV++)=r.l*mMatrix[pDesktop](0,0)+mMatrix[pDesktop](0,2);
        *(lV++)=r.b*mMatrix[pDesktop](1,1)+mMatrix[pDesktop](1,2);

        *(lV++)=r.l;
        *(lV++)=r.b;
        *(lV++)=0;

        *(lV++)=r.r*mMatrix[pDesktop](0,0)+mMatrix[pDesktop](0,2);
        *(lV++)=r.b*mMatrix[pDesktop](1,1)+mMatrix[pDesktop](1,2);

        *(lV++)=r.r;
        *(lV++)=r.b;
        *(lV++)=0;

        *(lV++)=r.r*mMatrix[pDesktop](0,0)+mMatrix[pDesktop](0,2);
        *(lV++)=r.t*mMatrix[pDesktop](1,1)+mMatrix[pDesktop](1,2);

        *(lV++)=r.r;
        *(lV++)=r.t;
        *(lV++)=0;

        *(lV++)=r.l*mMatrix[pDesktop](0,0)+mMatrix[pDesktop](0,2);
        *(lV++)=r.t*mMatrix[pDesktop](1,1)+mMatrix[pDesktop](1,2);

        *(lV++)=r.l;
        *(lV++)=r.t;
        *(lV++)=0;

        *(lV++)=r.l*mMatrix[pDesktop](0,0)+mMatrix[pDesktop](0,2);
        *(lV++)=r.b*mMatrix[pDesktop](1,1)+mMatrix[pDesktop](1,2);

        *(lV++)=r.l;
        *(lV++)=r.b;
        *(lV++)=0;

        *(lV++)=r.r*mMatrix[pDesktop](0,0)+mMatrix[pDesktop](0,2);
        *(lV++)=r.t*mMatrix[pDesktop](1,1)+mMatrix[pDesktop](1,2);

        *(lV++)=r.r;
        *(lV++)=r.t;
        *(lV++)=0;
    }

    glEnableVertexAttribArray(3);
    glBufferData(GL_ARRAY_BUFFER,(lV-lVertices)*sizeof(float),lVertices,GL_DYNAMIC_DRAW);
    glVertexAttribPointer(3,2,GL_FLOAT,GL_FALSE,5*sizeof(GLfloat),nullptr);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,5*sizeof(GLfloat),(void*)(2*sizeof(GLfloat)));

    glDrawArrays(GL_TRIANGLES,0,6*pR.size());
    glDisableVertexAttribArray(3);
}

void CClient::DrawTexture(EDrawMode pFlags,const CRegion &pR,int pDesktop)
{
    if(pR.empty()) return;

    bool lFastFilter;

    if(pFlags&DRAW_TRANSFORMED)
    {
        //TODO: here we scale the window
        lFastFilter=false;
    }
    else if(pFlags&DRAW_SCREEN_TRANSFORMED)
    {
        lFastFilter=false;
    }
    else
    {
        lFastFilter=true;
    }

    lFastFilter=false;

    EnableTexture(lFastFilter);

    if(pFlags&DRAW_TRANSLUCENT)
    {
        glEnable(GL_BLEND);
//          glBlendFuncSeparate(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ZERO);
        glBlendFuncSeparate(GL_ONE,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ZERO);
        glVertexAttrib4d(1,mEffOpacity,mEffOpacity,mEffOpacity,mEffOpacity);

        DrawGeometry(pR,pDesktop);

        glVertexAttrib4f(1,0.0,0.0,0.0,0.0);
        glDisable(GL_BLEND);
    }
    else
    {
        glVertexAttrib3f(1,1,1,1);

        DrawGeometry(pR,pDesktop);
    }

    DisableTexture();
}

void CClient::DrawTextureRect(EDrawMode pFlags,const CRect_d &pRect,int pDesktop,
                    bool pMipmap)
{
    bool lFastFilter;

    glPushMatrix();

    lFastFilter=false;

    EnableTexture(lFastFilter,pMipmap);

    glEnable(GL_BLEND);
    if(pFlags&DRAW_TRANSLUCENT)
    {
//          glBlendFuncSeparate(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ZERO);
        glBlendFuncSeparate(GL_ONE,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ZERO);
        glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
        glColor4d(mEffOpacity,mEffOpacity,mEffOpacity,mEffOpacity);
    }
    else
    {
        glColor4f(1,1,1,1.0);
    }

    double lTexArray[8]={0,0,0,1,1,1,1,0};
    double lVertexArray[8]={pRect.l,pRect.t,pRect.l,pRect.b,
                            pRect.r,pRect.b,pRect.r,pRect.t};

    glVertexPointer(2,GL_DOUBLE,0,lVertexArray);
    glTexCoordPointer(2,GL_DOUBLE,0,lTexArray);

    glDrawArrays(GL_QUADS,0,4);

//  DrawGeometry(pRect,pDesktop);

    if(pFlags&DRAW_TRANSLUCENT)
    {
        glColor4f(0.0,0.0,0.0,0.0);
    }
    glDisable(GL_BLEND);

    DisableTexture();

    glPopMatrix();
}

//calculate the clipping area and drawing order upside down
void CClient::Clip(SGLCompositeContext &pC)
{
    //never painted, ignore it
    if (!GetFlag(FLAG_DAMAGED)||(!GetFlag(FLAG_MAPPED)&&!mAnimating)||!IsOnDesktop(pC.mDesktop))
    {
        return;
    }

    mRegions.mObscured[pC.mDesktop]=pC.mObscured;

    //what isn't covered on top
    mRegions.mRemaining=pC.mRemaining;
    mRegions.mRemaining&=mRegions.mWhole[pC.mDesktop];

    //we don't return yet if mRemaining is empty because mRemaining depends on the current desktop damage, while
    //mObscured is independent of it, and must also be updated
    
    if (mCompositeMode==COMPOSITE_SOLID)
    {
        if(0)       //non-transparent decoration
        {
            pC.mObscured|=mRegions.mWhole[pC.mDesktop];
            pC.mRemaining-=mRegions.mWhole[pC.mDesktop];
        }
        else
        {
            pC.mObscured|=mRegions.mClient[pC.mDesktop];
            pC.mRemaining-=mRegions.mClient[pC.mDesktop];
        }
    }

    //if something has to be painted, put on list
    if(!mRegions.mRemaining.empty())
    {
        mPrevTrans=pC.mPrevTrans;
        pC.mPrevTrans=this;
    }
}

//paint down->up
void CClient::Paint(SGLCompositeContext &pC)
{
    //if we are here we have to paint... we wouldn't be here if
    //remaining is empty
    if (!mTexture.mBound)
    {
        if(!CreateTexture())
            return;
    }

    //calculate client
    CRegion lClient=mRegions.mRemaining&mRegions.mClient[pC.mDesktop];
    //calculate remaining for decoration
    //right now we don't subtract the client region from the decoration, since we want the decoration to be able to
    //paint over the client'
//    mRegions.mRemaining.Subtract(mRegions.mClient[pC.mDesktop]);


    if(!lClient.empty())
    {
        if(mCompositeMode==COMPOSITE_SOLID)
        {
            DrawTexture(DRAW_SOLID,lClient,pC.mDesktop);
        }
        else
        {
            DrawTexture(DRAW_TRANSLUCENT,lClient,pC.mDesktop);
        }
/*
        if(GetFlag(FLAG_IS_BUOLA_DOCK))
        {
            for(int i=0;i<gRoot->mManagedClients.size();i++)
            {
                CManagedClient *lC=gRoot->mManagedClients[i];
                const CRect_i &lRect=lC->GetIconGeometry();
                if(lRect.IsUndefined()) continue;

                glPushMatrix();
                glTranslated((lRect.l+lRect.r)/2,lRect.b,0);

                CSize_d lSize=lC->GetPhysicalSize();
                double lScale=max(1.0,lSize.x/lRect.w());
                lScale=1.0/max(lScale,lSize.y/lRect.h());

                glScaled(lScale,lScale,1.0);
                glNormal3f(0,0,-1);
                lC->PaintThumbnail();
                glPopMatrix();
            }
        }
*/
    }

    //again, check is not necessary since we didn't change remaining region'
    if(mDecoration)//&&!mRegions.mRemaining.empty())
    {
        glEnable(GL_BLEND);
        glVertexAttrib4d(1,mEffOpacity,mEffOpacity,mEffOpacity,mEffOpacity);
        if(mDecoration)
            mDecoration->Paint(mRegions.mRemaining,pC.mDesktop);
        glVertexAttrib4f(1,0.0,0.0,0.0,0.0);
        glDisable(GL_BLEND);
    }
}

void CClient::PaintRect(const CRect_d &pRect)
{
    if (!mTexture.mBound)
    {
        if(!CreateTexture())
            return;
    }

    if(mCompositeMode==COMPOSITE_SOLID)
    {
        DrawTextureRect(DRAW_SOLID,pRect,0);
    }
    else
    {
        DrawTextureRect(DRAW_TRANSLUCENT,pRect,0);
    }
}

void CClient::PaintBaseLine(int pDesktop,bool pMipmap)
{
    if (!mTexture.mBound)
    {
        if(!CreateTexture())
            return;
    }

    glEnable(GL_BLEND);
    glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
    glColor4d(mEffOpacity,mEffOpacity,mEffOpacity,mEffOpacity);
    if(mDecoration)
        mDecoration->PaintBaseLine();
    glColor4f(0.0,0.0,0.0,0.0);
    glDisable(GL_BLEND);

    if(mCompositeMode==COMPOSITE_SOLID)
    {
        DrawTextureRect(DRAW_SOLID,CRect_d(
            CPoint_d(-mPhysicalSize.x/2,-mPhysicalSize.y),mPhysicalSize),0,pMipmap);
    }
    else
    {
        DrawTextureRect(DRAW_TRANSLUCENT,CRect_d(
            CPoint_d(-mPhysicalSize.x/2,-mPhysicalSize.y),mPhysicalSize),0,pMipmap);
    }
}

void CClient::PaintThumbnail()
{
    if (!mTexture.mBound)
    {
        if(!CreateTexture())
            return;
    }

    CRect_d lRect(CPoint_d(-mPhysicalSize.x/2,-mPhysicalSize.y),mPhysicalSize);

    EnableTexture(false,true);

    glEnable(GL_BLEND);

    glBlendFuncSeparate(GL_ONE,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ZERO);
    glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

    glColor4f(1,1,1,1.0);

    double lTexArray[8]={0,0,0,1,1,1,1,0};
    double lVertexArray[8]={lRect.l,lRect.t,lRect.l,lRect.b,
                            lRect.r,lRect.b,lRect.r,lRect.t};

    glVertexPointer(2,GL_DOUBLE,0,lVertexArray);
    glTexCoordPointer(2,GL_DOUBLE,0,lTexArray);

    glDrawArrays(GL_QUADS,0,4);

    glColor4f(0.3,0.3,0.3,0.3);

    double lTexArray2[8]={0,1,0,0,1,0,1,1};
    double lVertexArray2[8]={lRect.l,-lRect.b,lRect.l,-lRect.t,
                            lRect.r,-lRect.t,lRect.r,-lRect.b};

    glVertexPointer(2,GL_DOUBLE,0,lVertexArray2);
    glTexCoordPointer(2,GL_DOUBLE,0,lTexArray2);

    glDrawArrays(GL_QUADS,0,4);

    DisableTexture();

    //here, decoration could be painted, but thumbnail is decoration-less... maybe some shadow could be added,
    //but it doesn't make sense to paint the normal decoration
    glDisable(GL_BLEND);
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                          animation                            //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::StepFading(const CMonotonicTime &pTime)
{
    mEffOpacity=mFadeInfo->GetPos(pTime);

    DetermineMode();

    DamageWhole();
    //Do it at the end because it can destroy the object
    if(mFadeInfo->IsFinished(pTime))
    {
        delete mFadeInfo;
        mFadeInfo=nullptr;
        FinishAnimation();
    }
}

void CClient::StartFading(double pStart,double pFinish,const chrono::microseconds &pDuration)
{
    if(mFadeInfo) delete mFadeInfo;

    if(pFinish<0)
        pFinish=0;
    if(pFinish>1)
        pFinish=1;

    mFadeInfo=new SShapeRecovery(gRoot->GetTime().mCurrent,pDuration,pStart,pFinish);

    mEffOpacity=pStart;

    DetermineMode();
    DamageWhole();
}

void CClient::StartAnimation(EAnimation pAnim,const CRect_d &pRect)
{
    if(pAnim==ANIM_ACTIVATE)
    {
    }
    else if(pAnim==ANIM_FADEIN)
    {
        if(mAnimating==ANIM_FADEIN) return;
        StartFading(0,GetOpacity(),gConf.mFadeInTime);
    }
    else if(pAnim==ANIM_FADEOUT)
    {
        if(mAnimating==ANIM_FADEOUT) return;
        if(!mPixmap)
        {
            FinishAnimation();
            return;
        }
        StartFading(GetEffOpacity(),0,gConf.mFadeOutTime);
    }
    else if(pAnim==ANIM_FADECHANGE)
    {
        StartFading(GetEffOpacity(),GetOpacity(),
                gConf.mFadeTime);
    }
    else if(pAnim==ANIM_ICONIFY)
    {
    }
    else if(pAnim==ANIM_DEICONIFY)
    {
    }

    mAnimating=pAnim;
}

void CClient::StartAnimationDrag(const CPoint_d &pPos,gui::EDragState pDrag)
{
}

void CClient::StepAnimationDrag(const CPoint_d &pPos,const CSize_d &pSize)
{
}

void CClient::FinishAnimation()
{
    mAnimating=ANIM_NONE;

    if(!mID) //client destroyed
    {
        delete this;
        return;
    }

    if(!GetFlag(FLAG_MAPPED))
    {
        UnsetFlag(FLAG_DAMAGED);
        DamageWhole();
        DiscardPixmap();
    }
}

///////////////////////////////////////////////////////////////////
//                                                               //
//                       event handlers                          //
//                                                               //
///////////////////////////////////////////////////////////////////

void CClient::OnLoop(const CMonotonicTime &pTime)
{
    if(mFadeInfo)
    {
        StepFading(pTime);
    }
}

void CClient::OnDamage(const CRect_i &pRect)
{
    DamageRect(pRect+CPoint_i(mBorderWidth,mBorderWidth));
    SetFlag(FLAG_DAMAGED);
    ///\todo
    //    if(mTexture)
//        mTexture->InvalidateMipmap();
}

void *CClient::GetRendererData(int pSlot)
{
    if(pSlot>=mRendererData.size())
    {
        mRendererData.resize(pSlot+1,nullptr);
    }
    return mRendererData[pSlot];
}

void CClient::SetRendererData(int pSlot,void *pData)
{
    if(pSlot>=mRendererData.size())
    {
        mRendererData.resize(pSlot+1,nullptr);
    }
    mRendererData[pSlot]=pData;
}

/*namespace bwm*/ } /*namespace buola*/ }
